#include "city_map.h"
#include "city_path.h"
#include "city_node.h"
#include "city_editor.h"
#include "city_actor.h"


#include "dd/dd_man.h"

#include "background/bg_scene.h"
#include "font/font_manager.h"

#include "script/scripthelper.h"
#include <luabind/luabind.hpp>
#include "input/input.h"

#include "script/write_lua.h"
#include "graphics/graphics.h"

#include "util/timer.h"				// log
#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    

#include <cassert>
#include <algorithm>

using namespace background;
using namespace city;
using namespace script;


CityMap::CityMap() : 
	m_pBackground(0),
	m_pEditor(0),
	m_pTarget(NULL),
	m_Turn(2),
	m_CanScroll(true)
{
	//Load("test");
}


CityMap::~CityMap()
{
	Reset();
	for (unsigned int i=0; i<m_CityData.size(); i++)
		delete m_CityData[i];
}

void	CityMap::Reset()
{
	m_CurrentTurn = 0;
	UTIL_SAFE_DELETE(m_pEditor);
	UTIL_SAFE_DELETE(m_pBackground);
	m_pTarget = NULL;
	for (unsigned int i=0; i<m_Nodes.size(); i++)
		delete m_Nodes[i];
	for (unsigned int i=0; i<m_Paths.size(); i++)
		delete m_Paths[i];
	for (unsigned int i=0; i<m_Actors.size(); i++)
		delete m_Actors[i];

	m_Nodes.resize(0);
	m_Paths.resize(0);
	m_Actors.resize(0);

}

void	CityMap::RenderDebug()
{
	if (m_pEditor)
	{
		for (unsigned int i=0; i<m_Nodes.size(); i++)
		{
			m_Nodes[i]->RenderDebug(GetOffset());
		}
	}
	for (unsigned int i=0; i<m_Paths.size(); i++)
	{
		m_Paths[i]->RenderDebug(GetOffset());
	}
}

void	CityMap::RebuildGraph()
{
	m_Graph.Reset();
	
	// add all the nodes
	for (unsigned int i=0; i<m_Nodes.size(); i++)
	{
		CityNode*		pNode = m_Nodes[i];
		m_Graph.AddNode(pNode->GetID());
	}

	// add all the edges
	for (unsigned int i=0; i<m_Paths.size(); i++)
	{
		CityPath*		pNode = m_Paths[i];
		m_Graph.AddEdge(pNode->GetFrom(), pNode->GetTo());
	}
}

void	CityMap::Render()
{
	if (m_pBackground)
		m_pBackground->RenderBackground();

	// render out own stuff

	if (m_pBackground)	m_pBackground->RenderForeground();
	if (m_pEditor)	m_pEditor->Render();

//	RenderDebug();
	if (!m_pEditor)
	{
		for (unsigned int i=0; i<m_Nodes.size(); i++)
		{
			m_Nodes[i]->Render(GetOffset());
		}
		for (unsigned int i=0; i<m_Actors.size(); i++)
		{
			m_Actors[i]->Render();
		}
	}
}

void	CityMap::Tick(float dt)
{
	// approach
	if (m_pTarget)
	{
		m_AbsOffset = m_pTarget->GetPos() - math::Vec2(0.5f, 0.5f);
	}
	if (m_CanScroll)
	{
		float	Speed = 3.0f;
		m_Offset+= (m_AbsOffset-m_Offset)*dt*Speed;
	}
	UTIL_CLAMP(m_Offset.x, 0, GetMax().x);
	UTIL_CLAMP(m_Offset.y, 0, GetMax().y);
	UTIL_CLAMP(m_AbsOffset.x, 0, GetMax().x);
	UTIL_CLAMP(m_AbsOffset.y, 0, GetMax().y);

	if (m_pBackground)
	{
		m_pBackground->SetOffset(m_Offset);
		m_pBackground->Tick(dt);
	}

	m_FocusPoint = input::Input::Instance().GetMouse()->GetPos();
	if (m_CanScroll)
		MouseScroll(dt);


	if (IsEditing() || (m_pBackground && m_pBackground->GetEditor()))
	{

	} else
	{
		for (unsigned int i=0; i<m_Actors.size(); i++)
		{
			m_Actors[i]->Tick(dt);
			m_Actors[i]->SetProgress(m_Turn.GetProgress());
		}
		for (unsigned int i=0; i<m_Nodes.size(); i++)
		{
			m_Nodes[i]->Tick(dt);
		}
		for (unsigned int i=0; i<m_Paths.size(); i++)
		{
			m_Paths[i]->Tick(dt);
		}
		m_Turn.Tick(dt);
		if (m_Turn.IsReady())
		{
			m_CurrentTurn++;
			m_Turn.Reset();
			for (unsigned int i=0; i<m_Actors.size(); i++)
			{
				m_Actors[i]->TurnEnded();
			}
		}
	}
}

CityData*	CityMap::GetCityData(int ID)
{
	for (unsigned int i=0; i<m_CityData.size(); i++)
	{
		if (m_CityData[i]->ID == ID) return m_CityData[i];
	}
	return NULL;
}

bool	CityMap::LoadData(const std::string& File)
{
	for (unsigned int i=0; i<m_CityData.size(); i++)
		delete m_CityData[i];
	m_CityData.resize(0);
	LuaPlus::LuaState*	pScript = ScriptHelper::CreateLuaState();
	if (!ScriptHelper::DoFile(*pScript, File))
	{
		_LOG(MSG_ERROR, "Unable to load data " << File << "!");
		 LuaState::Destroy( pScript );
		return false;
	} else
	{
		LuaPlus::LuaObject		Nodes = ScriptHelper::GetObj(*pScript, "CityData");
		if (Nodes.IsTable())
		{
			for (int i=0; i<Nodes.GetTableCount(); i++)

			{
				LuaPlus::LuaObject		Node		= Nodes.GetByIndex(i+1);
				CityData*	pNewNode = new CityData;

				pNewNode->ID			= ScriptHelper::GetTableInt(Node, "ID", -1);
				pNewNode->Name			= ScriptHelper::GetTableString(Node, "Name");
				pNewNode->Description	= ScriptHelper::GetTableString(Node, "Description");
				pNewNode->Map			= ScriptHelper::GetTableString(Node, "Map");
				pNewNode->MouseOff		= ScriptHelper::GetTableString(Node, "MouseOff");
				pNewNode->MouseOver		= ScriptHelper::GetTableString(Node, "MouseOver");
				pNewNode->Script		= ScriptHelper::GetTableString(Node, "Script");
				pNewNode->Condition		= ScriptHelper::GetTableString(Node, "Condition");

				m_CityData.push_back(pNewNode);
			}
		}
	}
	return true;
}

bool	CityMap::Load(const std::string& File)
{
	Reset();
	LuaPlus::LuaState*	pScript = ScriptHelper::CreateLuaState();
	if (!ScriptHelper::DoFile(*pScript, File))
	{
		_LOG(MSG_ERROR, "Unable to load map file " << File << "!");
		 LuaState::Destroy( pScript );
		return false;
	} else
	{
		UTIL_SAFE_DELETE(m_pBackground);
		for (unsigned int i=0; i<m_Nodes.size(); i++)	delete m_Nodes[i];
		m_Nodes.resize(0);
		for (unsigned int i=0; i<m_Paths.size(); i++)	delete m_Paths[i];
		m_Paths.resize(0);


		m_pBackground = new BGScene();
		m_pBackground->LoadFile(*pScript);

		// load nodes
		LuaPlus::LuaObject		Nodes = ScriptHelper::GetObj(*pScript, "CityNodes");
		if (Nodes.IsTable())
		{
			for (int i=0; i<Nodes.GetTableCount(); i++)
			{
				LuaPlus::LuaObject		Node		= Nodes.GetByIndex(i+1);
				int						NodeID		= ScriptHelper::GetTableInt(Node, "ID", -1);
				int						Owner		= ScriptHelper::GetTableInt(Node, "Owner", 0);
				std::string				Type		= ScriptHelper::GetTableString(Node, "Type");
				int						Quality		= ScriptHelper::GetTableInt(Node, "Quality", 0);
				math::Vec2				Pos			= ScriptHelper::GetTableVec2(Node, "Pos");

				CityNode* pNode = AddNode(Pos, Type, Quality, NodeID);
				if (pNode)
				{
					pNode->SetOwner(Owner);
				}
			}
		}
		// load paths		
		LuaPlus::LuaObject		Paths = ScriptHelper::GetObj(*pScript, "CityPaths");
		if (Paths.IsTable())
		{
			for (int i=0; i<Paths.GetTableCount(); i++)
			{
				LuaPlus::LuaObject		Path		= Paths.GetByIndex(i+1);
				int						From		= ScriptHelper::GetTableInt(Path, "From", -1);
				int						To			= ScriptHelper::GetTableInt(Path, "To", -1);
				int						Type		= ScriptHelper::GetTableInt(Path, "Type", 0);
				math::SplineCache2D		NewSpline;
				LuaPlus::LuaObject		Spline		= Path.GetByName("Path");
				if (Spline.IsTable())
				{	
					for (int j=0; j<Spline.GetTableCount(); j++)
					{
						NewSpline.InsertNode(ScriptHelper::GetVec2(Spline.GetByIndex(j+1)));
					}
				}
				AddPath(NewSpline, From, To, Type);
			}
		}
	}
	LoadCustom(*pScript);
	RebuildGraph();
	GameLoaded();
	LuaState::Destroy( pScript );
	return true;
}

bool	CityMap::Save(const std::string& File)
{
	WriteLua	WL;
	WL.Open(File);

	if (m_pBackground)
		m_pBackground->SaveFile(WL);

	if (!m_Nodes.empty())
	{
		WL.StartTable("CityNodes");
		for (unsigned int i=0; i<m_Nodes.size(); i++)
		{
			WL.StartTable();
				CityNode* pNode = m_Nodes[i];
				WL.WritePair("ID",		pNode->GetID());
				WL.WritePair("Pos",		pNode->GetPos());
				WL.WritePair("Owner",	pNode->GetOwner());
				WL.WritePair("Type",	pNode->GetType());
				WL.WritePair("Quality", pNode->GetQuality());
			WL.CloseTable();
		}	
		WL.CloseTable(false);
	}

	if (!m_Paths.empty())
	{
		WL.StartTable("CityPaths");
		for (unsigned int i=0; i<m_Paths.size(); i++)
		{
			WL.StartTable();
				CityPath* pPath = m_Paths[i];
				WL.WritePair("Type", pPath->GetType());
				WL.WritePair("From", pPath->GetFrom());
				WL.WritePair("To", pPath->GetTo());
				WL.WritePair("Path", pPath->GetSpline());
			WL.CloseTable();
		}	
		WL.CloseTable(false);
	}

	SaveCustom(WL);

	return true;
}

math::Vec2	CityMap::GetMax()
{
	if (m_pBackground)
	{
		return m_pBackground->GetMax() - math::Vec2(1,1);
	}
	return math::Vec2(0,0);
}

void	CityMap::MouseScroll(float dt)
{
/*	if (!IsEditing()) return;
	if (IsEditing())
	{
		if (!m_EnableScroll) return;
//		if (ui::UIManager::Instance().MouseInside(m_FocusPoint)) 	return ;
	}*/


	// setup the scrolling
	const	float	ScrollBorderMinX = 30.0f/800.0f;
	const	float	ScrollBorderMaxX = 1.0f - ScrollBorderMinX ;
	const	float	ScrollBorderMinY = 30.0f/600.0f;
	const	float	ScrollBorderMaxY = 1.0f - ScrollBorderMinY;



	const	float	ScrollSpeed	 = 0.8f;

	if (m_FocusPoint.x < ScrollBorderMinX)
	{
		SetOffset(math::Vec2(m_AbsOffset.x - (ScrollSpeed*dt), m_AbsOffset.y));
	}else 
	if (m_FocusPoint.x > ScrollBorderMaxX)
	{
		SetOffset(math::Vec2(m_AbsOffset.x + (ScrollSpeed*dt), m_AbsOffset.y));
	} else
	{
		m_AbsOffset.x = m_Offset.x;
	}

	if (m_FocusPoint.y < ScrollBorderMinY)
	{
		SetOffset(math::Vec2(m_AbsOffset.x, m_AbsOffset.y - (ScrollSpeed*dt)));
	}
	else if (m_FocusPoint.y > ScrollBorderMaxY)
	{
		SetOffset(math::Vec2(m_AbsOffset.x, m_AbsOffset.y + (ScrollSpeed*dt)));
	}else
	{
		m_AbsOffset.y = m_Offset.y;
	}

}



void		CityMap::MouseButtonDown(input::Mouse::BUTTON_STATE* pKey, const math::Vec2& Pos)
{

}

void		CityMap::MouseButtonPressed(input::Mouse::BUTTON_STATE* pKey, const math::Vec2& Pos)
{
	if (m_pBackground)
	{
		m_pBackground->MouseButtonPressed(pKey, Pos);
		if (m_pBackground->GetEditor()) return;
	}
	if (m_pEditor)
	{
		m_pEditor->MouseButtonPressed(pKey, Pos);
		return;
	}
	// find the City we're over
	for (unsigned int i=0; i<m_Nodes.size(); i++)
	{
		CityNode*	pNode = m_Nodes[i];
		if (pNode->IsMouseOver(m_Offset))
		{
			NodeClicked(pNode);
			m_Turn.Reset();
		}
	}
}

void		CityMap::NodeClicked(CityNode* pNode)
{
	if (pNode)
		pNode->OnClick();

}

void		CityMap::MouseMove(const math::Vec2& From, const math::Vec2& To)
{
	if (m_pBackground)
	{
		m_pBackground->MouseMove(From, To);
		if (m_pBackground->GetEditor()) return;
	}
	if (m_pEditor)
	{
		m_pEditor->MouseMove(From, To);
		return;
	}
}

void		CityMap::MouseWheelMove(int WheelInfo, const math::Vec2& Pos)
{
	if (m_pBackground)
	{
		m_pBackground->MouseWheelMove(WheelInfo, Pos);
		if (m_pBackground->GetEditor()) return;
	}
	if (m_pEditor)
	{
		m_pEditor->MouseWheelMove(WheelInfo, Pos);
		return;
	}
}

void		CityMap::KeyPressed(input::Keyboard::KEY* pKey)
{
	if (m_pBackground)
	{
		m_pBackground->KeyPressed(pKey);
		if (m_pBackground->GetEditor()) return;
	}

	if (pKey->ScanCode == DIK_F1)
	{
		if (m_pEditor)	
		{
			UTIL_SAFE_DELETE(m_pEditor);
			RebuildGraph();
		}
		else
		{
			m_pEditor = new CityEditor(this);
		}
	}
	if (m_pEditor)
	{
		m_pEditor->KeyPressed(pKey);
		return;
	}
}

void		CityMap::KeyDown(input::Keyboard::KEY* pKey)
{
	if (m_pBackground)
	{
		m_pBackground->KeyDown(pKey);
		if (m_pBackground->GetEditor()) return;
	}
	if (m_pEditor)
	{
		m_pEditor->KeyDown(pKey);
		return;
	}

}


bool				CityMap::AddPath(const math::SplineCache2D& Spline, int From, int To, int Type)
{
	CityPath* pPath = new CityPath(this, Type);
	pPath->CreateFromSpline(Spline);
	pPath->SetFrom(From);
	pPath->SetTo(To);
	m_Paths.push_back(pPath);
	return true;
}

CityPath*			CityMap::GetPath(const math::Vec2& Pos)
{
	for (unsigned int i=0; i<m_Paths.size(); i++)
	{
		if (m_Paths[i]->HitTest(Pos))
		{
			return m_Paths[i];
		}
	}
	return NULL;
}

bool				CityMap::DeletePath(const math::Vec2& Pos)
{
	for (unsigned int i=0; i<m_Paths.size(); i++)
	{
		if (m_Paths[i]->HitTest(Pos))
		{
			delete m_Paths[i];
			m_Paths.erase(m_Paths.begin() + i);
			return true;
		}
	}
	return false;
}

CityPath*		CityMap::GetConnection(CityNode*	pA, CityNode*	pB, bool & Reverse)
{
	if (!pA || !pB) return NULL;
	int		A = pA->GetID();
	int		B = pB->GetID();
	Reverse = false;
	for (unsigned int i=0; i<m_Paths.size(); i++)
	{
		CityPath*		pPath = m_Paths[i];
		if (pPath->TestEnds(A, B))
		{	
			if (pPath->GetFrom()==B)
				Reverse = true;
			return m_Paths[i];
		}
	}
	return NULL;
}

CityNode*			CityMap::AddNode(const math::Vec2& Pos, const std::string& Type, int Quality, int ID)
{
	if (ID == -1)
	{
		for (unsigned int i =0; i<1000; i++)
		{		
			ID = i;
			if (!GetNode(ID))
			{
				break;
			}
		}
	}
	CityNode*			pNode = new CityNode(ID, this);
	pNode->SetType(Type);
	pNode->SetQuality(Quality);
	pNode->SetPos(Pos);
	m_Nodes.push_back(pNode);
	return pNode;
}

bool				CityMap::DeleteNode(const math::Vec2& Pos)
{
	for (unsigned int i=0; i<m_Nodes.size(); i++)
	{
		if (m_Nodes[i]->HitTest(Pos))
		{
			CityNode*		pNode = m_Nodes[i];
			m_Nodes.erase(m_Nodes.begin()+i);

			// delete all paths that has it as foundation as well
			for (int j=(int)m_Paths.size()-1; j>=0; j--)
			{
				if (m_Paths[j]->GetFrom() == pNode->GetID() || 
					m_Paths[j]->GetTo() == pNode->GetID())
				{
					delete m_Paths[j];
					m_Paths.erase(m_Paths.begin() + j);
				}
			}

			delete pNode;
			return true;
		}
	}
	return false;
}

CityNode*			CityMap::GetNode(const math::Vec2& Pos)
{
	for (unsigned int i=0; i<m_Nodes.size(); i++)
	{
		if (m_Nodes[i]->HitTest(Pos))
		{
			return m_Nodes[i];
		}
	}
	return NULL;
}
CityNode*			CityMap::GetNode(int ID)
{
	for (unsigned int i=0; i<m_Nodes.size(); i++)
	{
		if (m_Nodes[i]->GetID()==ID)
		{
			return m_Nodes[i];
		}
	}
	return NULL;
}

CityNode*			CityMap::GetNode(const std::string& Name)
{
	for (unsigned int i=0; i<m_Nodes.size(); i++)
	{
		if (m_Nodes[i]->TestName(Name))
		{
			return m_Nodes[i];
		}
	}
	return NULL;
}


void				CityMap::Register(LuaPlus::LuaState* _pScript)
{
//	BGEditor::Register(_pScript);
	// assume that the editor is accessed into script 
    lua_State * L = _pScript->GetCState ( );
	luabind::module(L)
    [
    luabind::class_<CityMap>( "CityMap" )
		.def("Save",			Save)
		.def("Load",			Load)
	];
}

